//------------------------------------------------------------------------------
// <copyright file="ConfigurationManager.cs" company="Microsoft">
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
//------------------------------------------------------------------------------

namespace System.Configuration {

    using System.Collections;
    using System.Collections.Specialized;
    using System.Globalization;
    using System.Configuration.Internal;
    using System.IO;
    using System.Security.Permissions;
    using System.Threading;

    public static class ConfigurationManager {
        // The Configuration System
        private static volatile IInternalConfigSystem s_configSystem;

        // Initialization state
        private static volatile InitState       s_initState;
        private static object                   s_initLock;
        private static volatile Exception       s_initError;

        private enum InitState  {
            // Initialization has not yet started.
            NotStarted = 0,

            // Initialization has started.
            Started,

            // The config system can be used, but initialization is not yet complete.
            Usable,

            // The config system has been completely initialized.
            Completed
        };

        static ConfigurationManager() {
            s_initState = InitState.NotStarted;
            s_initLock = new object();
        }

        // to be used by System.Diagnostics to avoid false config results during config init
        internal static bool SetConfigurationSystemInProgress {
            get {
                return InitState.NotStarted < s_initState && s_initState < InitState.Completed;
            }
        }

        // Called by ASP.NET to allow hierarchical configuration settings and ASP.NET specific extenstions.
        internal static void SetConfigurationSystem(IInternalConfigSystem configSystem, bool initComplete) {
            lock (s_initLock) {
                // It is an error if the configuration system has already been set.
                if (s_initState != InitState.NotStarted) {
                    throw new InvalidOperationException(SR.GetString(SR.Config_system_already_set));
                }

                s_configSystem = configSystem;
                if (initComplete) {
                    s_initState = InitState.Completed;
                }
                else {
                    s_initState = InitState.Usable;
                }
            }
        }

        private static void EnsureConfigurationSystem() {
            // If a configuration system has not yet been set, 
            // create the DefaultConfigurationSystem for exe's.
            lock (s_initLock) {
                if (s_initState < InitState.Usable) {
                    s_initState = InitState.Started;
                    try {
                        try {
                            // Create the system, but let it initialize itself
                            // when GetConfig is called, so that it can handle its
                            // own re-entrancy issues during initialization.
                            // When initialization is complete, the DefaultConfigurationSystem
                            // will call CompleteConfigInit to mark initialization as
                            // having completed.
                            // Note: the ClientConfigurationSystem has a 2-stage initialization,
                            // and that's why s_initState isn't set to InitState.Completed yet.
                            s_configSystem = new ClientConfigurationSystem();
                            s_initState = InitState.Usable;
                        }
                        catch (Exception e) {
                            s_initError = new ConfigurationErrorsException(SR.GetString(SR.Config_client_config_init_error), e);
                            throw s_initError;
                        }
                    }
                    catch {
                        s_initState = InitState.Completed;
                        throw;
                    }
                }
            }
        }

        // Set the initialization error.
        internal static void SetInitError(Exception initError) {
            s_initError = initError;
        }

        // Mark intiailization as having completed.
        internal static void CompleteConfigInit() {
            lock (s_initLock) {
                s_initState = InitState.Completed;
            }
        }


        private static void PrepareConfigSystem() {
            // Ensure the configuration system is usable.
            if (s_initState < InitState.Usable) {
                EnsureConfigurationSystem();
            }

            // If there was an initialization error, throw it.
            if (s_initError != null) {
                throw s_initError;
            }
        }

        internal static bool SupportsUserConfig {
            get {
                PrepareConfigSystem();

                return s_configSystem.SupportsUserConfig;
            }
        }

        //
        // *************************************************
        // ** Static Runtime Functions to retrieve config **
        // *************************************************
        //
        
        public static NameValueCollection AppSettings {
            get {
                object section = GetSection("appSettings");
                if (section == null || !(section is NameValueCollection)) {
                    // If config is null or not the type we expect, the declaration was changed. 
                    // Treat it as a configuration error.
                    throw new ConfigurationErrorsException(SR.GetString(SR.Config_appsettings_declaration_invalid));
                }

                return (NameValueCollection) section;
            }
        }

        public static ConnectionStringSettingsCollection ConnectionStrings { 
            get {
                object section = GetSection("connectionStrings");

                // Verify type, and return the collection 
                if (section == null || section.GetType() != typeof(ConnectionStringsSection)) {
                    // If config is null or not the type we expect, the declaration was changed. 
                    // Treat it as a configuration error.
                    throw new ConfigurationErrorsException(SR.GetString(SR.Config_connectionstrings_declaration_invalid));
                }

                ConnectionStringsSection connectionStringsSection = (ConnectionStringsSection) section;
                return connectionStringsSection.ConnectionStrings;
            }
        }
    
        // GetSection
        //
        // Retrieve a Section from the configuration system
        //
        public static object GetSection(string sectionName) {
            // Avoid unintended AV's by ensuring sectionName is not empty.
            // For compatibility, we cannot throw an InvalidArgumentException.
            if (String.IsNullOrEmpty(sectionName)) {
                return null;
            }

            PrepareConfigSystem();

            object section = s_configSystem.GetSection(sectionName);
            return section;
        }

        public static void RefreshSection(string sectionName) {
            // Avoid unintended AV's by ensuring sectionName is not empty.
            // For consistency with GetSection, we should not throw an InvalidArgumentException.
            if (String.IsNullOrEmpty(sectionName)) {
                return;
            }

            PrepareConfigSystem();

            s_configSystem.RefreshConfig(sectionName);                
        }

        //
        // *************************************************
        // ** Static Management Functions to edit config **
        // *************************************************
        //

        //
        // OpenMachineConfiguration
        //
        public static Configuration OpenMachineConfiguration() {
            return OpenExeConfigurationImpl(null, true, ConfigurationUserLevel.None, null);
        }

        public static Configuration OpenMappedMachineConfiguration(ConfigurationFileMap fileMap) {
            return OpenExeConfigurationImpl(fileMap, true, ConfigurationUserLevel.None, null);
        }

        //
        // OpenExeConfiguration
        //
        public static Configuration OpenExeConfiguration(ConfigurationUserLevel userLevel) {
            return OpenExeConfigurationImpl(null, false, userLevel, null);
        }

        public static Configuration OpenExeConfiguration(string exePath) {
            return OpenExeConfigurationImpl(null, false, ConfigurationUserLevel.None, exePath);
        }

        public static Configuration OpenMappedExeConfiguration(ExeConfigurationFileMap fileMap, ConfigurationUserLevel userLevel) {
            return OpenExeConfigurationImpl(fileMap, false, userLevel, null);
        }

        public static Configuration OpenMappedExeConfiguration(ExeConfigurationFileMap fileMap, ConfigurationUserLevel userLevel, bool preLoad) {
            return OpenExeConfigurationImpl(fileMap, false, userLevel, null, preLoad);
        }

        private static Configuration OpenExeConfigurationImpl(ConfigurationFileMap fileMap, bool isMachine, ConfigurationUserLevel userLevel, string exePath, bool preLoad = false) {
            // exePath must be specified if not running inside ClientConfigurationSystem
            if ( !isMachine &&
                 ( ( ( fileMap == null ) && ( exePath == null ) ) ||
                   ( ( fileMap != null ) && ( ( ( ExeConfigurationFileMap ) fileMap ).ExeConfigFilename == null ) )
                 )
               ) {
               if ( ( s_configSystem != null ) &&
                    ( s_configSystem.GetType() != typeof( ClientConfigurationSystem ) ) ) {
                   throw new ArgumentException(SR.GetString(SR.Config_configmanager_open_noexe));
               }
            }
            
            Configuration config = ClientConfigurationHost.OpenExeConfiguration(fileMap, isMachine, userLevel, exePath);
            if (preLoad) {
                PreloadConfiguration(config);
            }
            return config;
        }

        /// <summary>
        /// Recursively loads configuration section groups and sections belonging to a configuration object.
        /// </summary>
        private static void PreloadConfiguration(Configuration configuration) {
            if (null == configuration) {
                return;
            }

            // Preload root-level sections.
            foreach (ConfigurationSection section in configuration.Sections) {
            }

            // Recursively preload all section groups and sections.
            foreach (ConfigurationSectionGroup sectionGroup in configuration.SectionGroups) {
                PreloadConfigurationSectionGroup(sectionGroup);
            }
        }

        private static void PreloadConfigurationSectionGroup(ConfigurationSectionGroup sectionGroup) {
            if (null == sectionGroup) {
                return;
            }

            // Preload sections just by iterating.
            foreach (ConfigurationSection section in sectionGroup.Sections) {
            }

            // Load child section groups.
            foreach (ConfigurationSectionGroup childSectionGroup in sectionGroup.SectionGroups) {
                PreloadConfigurationSectionGroup(childSectionGroup);
            }
        }
    }
}
